package com.timsmeet.timeperiod;

public class TimeInterval implements Comparable<TimeInterval> {

	static final int MINUTES_PER_HOUR = 60;
	static final int MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR;
	
	static final String ADD_OVERFLOW_MSG = "overflow: add";
	static final String SUBSTRACT_OVERFLOW_MSG = "overflow: substract";

	private long minutes = 0;
	
	public TimeInterval(long minutes) {
		this.minutes = minutes;
	}
	
	public TimeInterval(int hours, int minutes) {
		this(0, hours, minutes);
	}
	
	public TimeInterval(int days, int hours, int minutes) {
		this.minutes =
			days * MINUTES_PER_DAY +
			hours * MINUTES_PER_HOUR +
			minutes;
	}
	
	public static TimeInterval fromDays(int days) {
		return new TimeInterval(days, 0, 0);
	}
	
	public static TimeInterval fromHours(int hours) {
		return new TimeInterval(hours, 0);
	}

	public static TimeInterval fromMinutes(int minutes) {
		return new TimeInterval(minutes);
	}

	public TimeInterval add(TimeInterval timeInterval) {
		this.minutes = addAndCheck(this.minutes, timeInterval.minutes, TimeInterval.ADD_OVERFLOW_MSG);
		return this;
	}
	
	private static long subAndCheck(long a, long b, String msg) {
		long ret;
		if (b == Long.MIN_VALUE) {
			if (a < 0) {
				ret = a - b;
			} else {
				throw new ArithmeticException(msg);
			}
		} else {
			// use additive inverse
			ret = addAndCheck(a, -b, msg);
		}
		return ret;
	}
	
	private static long addAndCheck(long a, long b, String msg) {
		long ret;
		if (a > b) {
			// use symmetry to reduce boundry cases
			ret = addAndCheck(b, a, msg);
		} else {
			// assert a <= b

			if (a < 0) {
				if (b < 0) {
					// check for negative overflow
					if (Long.MIN_VALUE - b <= a) {
						ret = a + b;
					} else {
						throw new ArithmeticException(msg);
					}
				} else {
					// oppisite sign addition is always safe
					ret = a + b;
				}
			} else {
				// assert a >= 0
				// assert b >= 0

				// check for positive overflow
				if (a <= Long.MAX_VALUE - b) {
					ret = a + b;
				} else {
					throw new ArithmeticException(msg);
				}
			}
		}
		return ret;
	}
	
	public TimeInterval substract(TimeInterval timeInterval) {
		this.minutes = subAndCheck(this.minutes, timeInterval.minutes, TimeInterval.SUBSTRACT_OVERFLOW_MSG);
		return this;
	}

	public int getDays() {
		return (int)(minutes / MINUTES_PER_DAY); 
	}
	
	public int getHours() {
		return (int)(minutes / MINUTES_PER_HOUR); 
	}
	
	public long getMinutes() {
		return minutes; 
	}
	
	public double getTotalDays() {
		return (double)minutes / MINUTES_PER_DAY;
	}
	
	public double getTotalHours() {
		return (double)minutes / MINUTES_PER_HOUR;
	}
	
	public int compareTo(TimeInterval timeInterval) {
		return (this.minutes > timeInterval.minutes) ? 1 : (this.minutes == timeInterval.minutes) ? 0 : -1;
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (int) (minutes ^ (minutes >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		TimeInterval other = (TimeInterval) obj;
		if (minutes != other.minutes)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "TimeInterval [minutes=" + minutes + "]";
	}
	
}
